﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using KC.Storage.Util.Extension;

namespace KC.Storage.BlobService
{
    internal class AzureWriter : WriterBase
    {
        private readonly CloudStorageAccount Account;
        private readonly CloudBlobClient Client;
        private readonly AzureCache Cache;

        public AzureWriter(string connectionString, AzureCache cache)
            : base()
        {
            if (string.IsNullOrWhiteSpace(connectionString))
                throw new ArgumentException("Azure Storage's connect string is empy or null.", "connectionString");

            this.Account = CloudStorageAccount.Parse(connectionString);
            this.Client = this.Account.CreateCloudBlobClient();
            this.Cache = cache;
        }

        private CloudBlob GetBlob(string containerName, string blobId, bool isBlockBlob = false)
        {
            if (string.IsNullOrWhiteSpace(containerName))
            {
                //return isBlockBlob ? this.Client.GetBlockBlobReference(blobId) : this.Client.GetBlobReference(blobId);
                return null;
            }
            else
            {
                CloudBlobContainer container = this.Client.GetContainerReference(containerName);
                container.CreateIfNotExistsAsync();
                return isBlockBlob ? container.GetBlockBlobReference(blobId) : container.GetBlobReference(blobId);
            }
        }

        protected override void CreateContainer(string containerName)
        {
            CloudBlobContainer container = this.Client.GetContainerReference(containerName);
            container.CreateIfNotExistsAsync();
        }

        protected async override void DeleteContainer(string containerName)
        {
            CloudBlobContainer container = this.Client.GetContainerReference(containerName);
            try
            {
                await container.DeleteAsync();
            }
            catch (StorageException)
            {
                //if (e.ErrorCode == StorageErrorCode.ResourceNotFound)
                //    return;
                //else
                //    throw;
            }
        }

        protected override void WriteBlob(string container, string blobId, byte[] blobData, Dictionary<string, string> blobMetadata)
        {
            var blob = GetBlob(container, blobId, true) as CloudBlockBlob;
            blob.UploadFromByteArrayAsync(blobData, 0, blobData.Length);
            if (blobMetadata != null)
            {
                foreach (string metadataKey in blobMetadata.Keys)
                {
                    blob.Metadata[metadataKey] = blobMetadata[metadataKey];
                }
                blob.SetMetadataAsync();
            }
        }

        protected async override void WriteBlobMetadata(string container, string blobId, bool clearExisting, Dictionary<string, string> blobMetadata)
        {
            CloudBlob blob = GetBlob(container, blobId);
            try
            {
                await blob.FetchAttributesAsync();
            }
            catch (Exception)
            {
                // Somehow this is happening in debug mode sometimes.
            }
            if (clearExisting)
            {
                blob.Metadata.Clear();
            }
            foreach (string metadataKey in blobMetadata.Keys)
            {
                blob.Metadata[metadataKey] = blobMetadata[metadataKey];
            }
            await blob.SetMetadataAsync();
        }

        protected async override void WriteBlobBlock(string container, string blobId, string blobBlockId, byte[] blobBlockData, string contentMD5 = null)
        {
            CloudBlockBlob blob = GetBlob(container, blobId, true) as CloudBlockBlob;
            await blob.PutBlockAsync(blobBlockId, new MemoryStream(blobBlockData), contentMD5/*, new BlobRequestOptions() { ServerTimeout = new TimeSpan(0, 10, 0) }*/);
        }

        protected async override void SubmitBlobBlock(string container, string blobId, string[] blobBlockIds, Dictionary<string, string> blobMetadata)
        {
            CloudBlockBlob blob = GetBlob(container, blobId, true) as CloudBlockBlob;
            await blob.PutBlockListAsync(blobBlockIds);
            if (blobMetadata != null)
            {
                foreach (string metadataKey in blobMetadata.Keys)
                {
                    blob.Metadata[metadataKey] = blobMetadata[metadataKey];
                }
                await blob.SetMetadataAsync();
            }
        }

        protected async override void DeleteBlob(string container, string blobId)
        {
            string realBlobId = GetActualBlobId(blobId);
            if (!string.IsNullOrWhiteSpace(realBlobId))
            {
                var blob = GetBlob(container, realBlobId);
                if (blob != null)
                {
                    try
                    {
                        await blob.DeleteIfExistsAsync();
                    }
                    catch (Exception)
                    {
                        try
                        {
                            // try again
                            await blob.BreakLeaseAsync(null);
                            await blob.DeleteIfExistsAsync();
                        }
                        catch (Exception)
                        {
                            throw;
                        }
                    }

                }
                this.Cache.Delete(container, realBlobId);
            }
        }

        #region Copy Blob

        public override void CopyBlob(string containerName, string desContainerName, string blobId)
        {
            if (string.IsNullOrEmpty(blobId)) return;

            //this.Client.RetryPolicy = RetryPolicies.Retry(20, TimeSpan.Zero);
            CloudBlobContainer container = this.Client.GetContainerReference(containerName);
            if (container != null)
            {
                var blob = GetBlob(containerName, blobId, true) as CloudBlockBlob;
                if (blob != null)
                {
                    var newBlob = GetBlob(desContainerName, blobId, true) as CloudBlockBlob;
                    //newBlob.UploadFromByteArrayAsync(new byte[] { }, 0, 0);
                    newBlob.StartCopyAsync(blob);
                }
                
            }
        }

        #endregion

        private static Dictionary<string, AutoRenewLease> _acquiredAutoRenewLeaseMap = new Dictionary<string, AutoRenewLease>();

        public bool IsBlobLocked(string containerName, string blobId)
        {
            lock (_acquiredAutoRenewLeaseMap)
            {
                if (_acquiredAutoRenewLeaseMap.ContainsKey(containerName + "|" + blobId))
                {
                    return false; // if current process has already get lease, it is not "locked" for itself
                }
            }

            CloudBlob blob = GetBlob(containerName, blobId, false);
            string tempLease = blob.AcquireLeaseAsync(null).Result;
            if (String.IsNullOrEmpty(tempLease))
            {
                return true;
            }
            blob.ReleaseLeaseAsync(AccessCondition.GenerateLeaseCondition(tempLease));
            return false;
        }

        public bool ObtainBlobLock(string containerName, string blobId)
        {
            var blob = GetBlob(containerName, blobId, true) as CloudBlockBlob;
            AutoRenewLease arl = new AutoRenewLease(blob);
            if (arl.HasLease)
            {
                lock (_acquiredAutoRenewLeaseMap)
                {
                    if (!_acquiredAutoRenewLeaseMap.ContainsKey(containerName + "|" + blobId))
                    {
                        _acquiredAutoRenewLeaseMap.Add(containerName + "|" + blobId, arl);
                    }
                }
                return true;
            }
            else
            {
                return false;
            }
        }

        public void ReleaseBlobLock(string containerName, string blobId)
        {
            lock (_acquiredAutoRenewLeaseMap)
            {
                if (_acquiredAutoRenewLeaseMap.ContainsKey(containerName + "|" + blobId))
                {
                    var arl = _acquiredAutoRenewLeaseMap[containerName + "|" + blobId];
                    _acquiredAutoRenewLeaseMap.Remove(containerName + "|" + blobId);
                    arl.Dispose();
                }
            }
        }

        public void BreakBlobLock(string containerName, string blobId)
        {
            CloudBlob blob = GetBlob(containerName, blobId);
            blob.BreakLeaseAsync(null);
        }
    }
}